﻿using OpenTK;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Windows.Forms;
using LightCAD.MathLib;

namespace LightCAD.Three
{
    public class OrbitControls : EventDispatcher
        {
            private static class STATE
            {
                public const int NONE = -1;
                public const int ROTATE = 0;
                public const int DOLLY = 1;
                public const int PAN = 2;
                public const int TOUCH_ROTATE = 3;
                public const int TOUCH_PAN = 4;
                public const int TOUCH_DOLLY_PAN = 5;
                public const int TOUCH_DOLLY_ROTATE = 6;
            }


            public Camera _object;
            public GLControl glControl;
            public bool enabled;
            public Vector3 target;
            public double minDistance;
            public double maxDistance;
            public double minZoom;
            public double maxZoom;
            public double minPolarAngle;
            public double maxPolarAngle;
            public double minAzimuthAngle;
            public double maxAzimuthAngle;
            public bool enableDamping;
            public double dampingFactor;
            public bool enableZoom;
            public double zoomSpeed;
            public bool enableRotate;
            public double rotateSpeed;
            public bool enablePan;
            public double panSpeed;
            public bool screenSpacePanning;
            public double keyPanSpeed;
            public bool autoRotate;
            public double autoRotateSpeed;
            //public object keys;
            //public object mouseButtons;
            //public object touches;
            public Vector3 target0;
            public Vector3 position0;
            public double zoom0;
            public object _domElementKeyEvents;

            //
            // internals
            //

            private int state = STATE.NONE;
            private double EPS = 0.000001;

            // current position in spherical coordinates
            private Spherical spherical = new Spherical();
            private Spherical sphericalDelta = new Spherical();

            private double scale = 1;
            private Vector3 panOffset = new Vector3();
            private bool zoomChanged = false;

            private Vector2 rotateStart = new Vector2();
            private Vector2 rotateEnd = new Vector2();
            private Vector2 rotateDelta = new Vector2();

            private Vector2 panStart = new Vector2();
            private Vector2 panEnd = new Vector2();
            private Vector2 panDelta = new Vector2();

            private Vector2 dollyStart = new Vector2();
            private Vector2 dollyEnd = new Vector2();
            private Vector2 dollyDelta = new Vector2();

            private ListEx<object> pointers = new ListEx<object>();
            private JsObj<object> pointerPositions = new JsObj<object>();

            private EventArgs _changeEvent = new EventArgs { type = "change" };
            private EventArgs _startEvent = new EventArgs { type = "start" };
            private EventArgs _endEvent = new EventArgs { type = "end" };

            public OrbitControls(Camera _object, GLControl glControl,Vector3 initTarget=null)
            {
                this._object = _object;
                this.glControl = glControl;

                // Set to false to disable this control
                this.enabled = true;

                // "target" sets the location of focus, where the object orbits around
                this.target = new Vector3();
                if (initTarget != null)
                    target.Copy(initTarget);
                // How far you can dolly in and out ( PerspectiveCamera only )
                this.minDistance = 0;
                this.maxDistance = double.MaxValue;

                // How far you can zoom in and out ( OrthographicCamera only )
                this.minZoom = 0;
                this.maxZoom = double.MaxValue;

                // How far you can orbit vertically, upper and lower limits.
                // Range is 0 to Math.PI radians.
                this.minPolarAngle = 0; // radians
                this.maxPolarAngle = Math.PI; // radians

                // How far you can orbit horizontally, upper and lower limits.
                // If set, the interval [ min, max ] must be a sub-interval of [ - 2 PI, 2 PI ], with ( max - min < 2 PI )
                this.minAzimuthAngle = double.MinValue; // radians
                this.maxAzimuthAngle = double.MaxValue; // radians

                // Set to true to enable damping (inertia)
                // If damping is enabled, you must call controls.update() in your animation loop
                this.enableDamping = false;
                this.dampingFactor = 0.05;

                // This option actually enables dollying in and out; left as "zoom" for backwards compatibility.
                // Set to false to disable zooming
                this.enableZoom = true;
                this.zoomSpeed = 1.0;

                // Set to false to disable rotating
                this.enableRotate = true;
                this.rotateSpeed = 1.0;

                // Set to false to disable panning
                this.enablePan = true;
                this.panSpeed = 1.0;
                this.screenSpacePanning = true; // if false, pan orthogonal to world-space direction camera.up
                this.keyPanSpeed = 7.0; // pixels moved per arrow key push

                // Set to true to automatically rotate around the target
                // If auto-rotate is enabled, you must call controls.update() in your animation loop
                this.autoRotate = false;
                this.autoRotateSpeed = 2.0; // 30 seconds per orbit when fps is 60

                // The four arrow keys
                //this.keys = new { LEFT = "ArrowLeft", UP = "ArrowUp", RIGHT = "ArrowRight", BOTTOM = "ArrowDown" };

                // Mouse buttons
                //this.mouseButtons = null;// { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.PAN };

                // Touch fingers
                //this.touches = null;// { ONE: TOUCH.ROTATE, TWO: TOUCH.DOLLY_PAN };

                // for reset
                this.target0 = this.target.Clone();
                this.position0 = this._object.position.Clone();
                this.zoom0 = this._object.zoom;

                // the target DOM element for key events
                this._domElementKeyEvents = null;

                this.glControl.ContextMenuStripChanged += onContextMenu;
                this.glControl.MouseDown += onMouseDown;
                this.glControl.MouseMove += onMouseMove;
                this.glControl.MouseUp += onMouseUp;
                this.glControl.MouseWheel += onMouseWheel;
                this.glControl.KeyDown += onKeyDown;
                this.update();//初始化相机LookAt,避免相机跳跃
            }

            public double getPolarAngle()
            {
                return spherical.Phi;
            }

            public double getAzimuthalAngle()
            {
                return spherical.Theta;
            }

            public double getDistance()
            {

                return this._object.position.DistanceTo(this.target);

            }

            public void listenToKeyEvents()
            {
                this.glControl.KeyDown += onKeyDown;
                this._domElementKeyEvents = this.glControl;
            }

            public void saveState()
            {

                this.target0.Copy(this.target);
                this.position0.Copy(this._object.position);
                this.zoom0 = this._object.zoom;

            }

            public void reset()
            {

                this.target.Copy(this.target0);
                this._object.position.Copy(this.position0);
                this._object.zoom = this.zoom0;

                this._object.updateProjectionMatrix();
                this.dispatchEvent(_changeEvent);

                this.update();

                state = STATE.NONE;

            }

            // this method is exposed, but perhaps it would be better if we can make it private...
            public bool update()
            {
                var offset = new Vector3();

                // so camera.up is the orbit axis
                var quat = new Quaternion().SetFromUnitVectors(this._object.up, new Vector3(0, 1, 0));
                var quatInverse = quat.Clone().Invert();

                var lastPosition = new Vector3();
                var lastQuaternion = new Quaternion();


                var twoPI = 2 * Math.PI;

                var position = this._object.position;

                offset.Copy(position).Sub(this.target);

                // rotate offset to "y-axis-is-up" space
                offset.ApplyQuaternion(quat);

                // angle from z-axis around y-axis
                spherical.SetFromVector3(offset);

                if (this.autoRotate && state == STATE.NONE)
                {

                    rotateLeft(getAutoRotationAngle());

                }

                if (this.enableDamping)
                {

                    spherical.Theta += sphericalDelta.Theta * this.dampingFactor;
                    spherical.Phi += sphericalDelta.Phi * this.dampingFactor;

                }
                else
                {

                    spherical.Theta += sphericalDelta.Theta;
                    spherical.Phi += sphericalDelta.Phi;

                }

                // restrict theta to be between desired limits

                var min = this.minAzimuthAngle;
                var max = this.maxAzimuthAngle;

                if (MathEx.IsFinite(min) && MathEx.IsFinite(max))
                {

                    if (min < -Math.PI) min += twoPI; else if (min > Math.PI) min -= twoPI;

                    if (max < -Math.PI) max += twoPI; else if (max > Math.PI) max -= twoPI;

                    if (min <= max)
                    {

                        spherical.Theta = Math.Max(min, Math.Min(max, spherical.Theta));

                    }
                    else
                    {

                        spherical.Theta = (spherical.Theta > (min + max) / 2) ?
                            Math.Max(min, spherical.Theta) :
                            Math.Min(max, spherical.Theta);

                    }

                }

                // restrict phi to be between desired limits
                spherical.Phi = Math.Max(this.minPolarAngle, Math.Min(this.maxPolarAngle, spherical.Phi));

                spherical.MakeSafe();


                spherical.Radius *= scale;

                // restrict radius to be between desired limits
                spherical.Radius = Math.Max(this.minDistance, Math.Min(this.maxDistance, spherical.Radius));

                // move target to panned location

                if (this.enableDamping)
                {
                    this.target.AddScaledVector(panOffset, this.dampingFactor);
                }
                else
                {
                    this.target.Add(panOffset);
                }

                offset.SetFromSpherical(spherical);

                // rotate offset back to "camera-up-vector-is-up" space
                offset.ApplyQuaternion(quatInverse);

                position.Copy(this.target).Add(offset);

                this._object.lookAt(this.target);

                if (this.enableDamping)
                {
                    sphericalDelta.Theta *= (1 - this.dampingFactor);
                    sphericalDelta.Phi *= (1 - this.dampingFactor);
                    panOffset.MultiplyScalar(1 - this.dampingFactor);
                }
                else
                {
                    sphericalDelta.Set(0, 0, 0);
                    panOffset.Set(0, 0, 0);
                }

                scale = 1;

                // update condition is:
                // min(camera displacement, camera rotation in radians)^2 > EPS
                // using small-angle approximation cos(x/2) = 1 - x^2 / 8

                if (zoomChanged ||
                    lastPosition.DistanceToSquared(this._object.position) > EPS ||
                    8 * (1 - lastQuaternion.Dot(this._object.quaternion)) > EPS)
                {

                    this.dispatchEvent(_changeEvent);

                    lastPosition.Copy(this._object.position);
                    lastQuaternion.Copy(this._object.quaternion);
                    zoomChanged = false;

                    return true;

                }

                return false;
            }

            public void dispose()
            {

                //this.glControl.removeEventListener('contextmenu', onContextMenu);

                //this.glControl.removeEventListener('pointerdown', onPointerDown);
                //this.glControl.removeEventListener('pointercancel', onPointerCancel);
                //this.glControl.removeEventListener('wheel', onMouseWheel);

                //this.glControl.removeEventListener('pointermove', onPointerMove);
                //this.glControl.removeEventListener('pointerup', onPointerUp);

                this.glControl.ContextMenuStripChanged -= onContextMenu;
                this.glControl.MouseDown -= onMouseDown;
                this.glControl.MouseMove -= onMouseMove;
                this.glControl.MouseUp -= onMouseUp;
                this.glControl.MouseWheel -= onMouseWheel;
                this.glControl.KeyDown -= onKeyDown;

                if (this._domElementKeyEvents != null)
                {

                    //this._domElementKeyEvents.removeEventListener("keydown", onKeyDown);

                }

                //this.dispatchEvent( { type: 'dispose' } ); // should this be added here?

            }

            private double getAutoRotationAngle()
            {

                return 2 * MathEx.PI / 60 / 60 * this.autoRotateSpeed;

            }

            private double getZoomScale()
            {

                return Math.Pow(0.95, this.zoomSpeed);

            }

            private void rotateLeft(double angle)
            {

                sphericalDelta.Theta -= angle;

            }

            private void rotateUp(double angle)
            {

                sphericalDelta.Phi -= angle;

            }

            private void panLeft(double distance, Matrix4 objectMatrix)
            {
                var v = new Vector3();
                v.SetFromMatrixColumn(objectMatrix, 0); // get X column of objectMatrix
                v.MultiplyScalar(-distance);

                panOffset.Add(v);
            }

            private void panUp(double distance, Matrix4 objectMatrix)
            {
                var v = new Vector3();
                if (this.screenSpacePanning)
                {

                    v.SetFromMatrixColumn(objectMatrix, 1);

                }
                else
                {

                    v.SetFromMatrixColumn(objectMatrix, 0);
                    v.CrossVectors(this._object.up, v);

                }

                v.MultiplyScalar(distance);

                panOffset.Add(v);

            }


            // deltaX and deltaY are in pixels; right and down are positive
            private void pan(double deltaX, double deltaY)
            {
                var offset = new Vector3();
                var element = this.glControl;

                if (this._object is PerspectiveCamera)
                {

                    var perCamera = this._object as PerspectiveCamera;
                    // perspective
                    var position = perCamera.position;
                    offset.Copy(position).Sub(this.target);
                    var targetDistance = offset.Length();

                    // half of the fov is center to top of screen
                    targetDistance *= Math.Tan((perCamera.fov / 2) * Math.PI / 180.0);

                    // we use only clientHeight here so aspect ratio does not distort speed
                    panLeft(2 * deltaX * targetDistance / element.Height, perCamera.matrix);
                    panUp(2 * deltaY * targetDistance / element.Height, perCamera.matrix);

                }
                else if (this._object is OrthographicCamera)
                {
                    var ortCamera = this._object as OrthographicCamera;
                    // orthographic
                    panLeft(deltaX * (ortCamera.right - ortCamera.left) / ortCamera.zoom / element.Width, ortCamera.matrix);
                    panUp(deltaY * (ortCamera.top - ortCamera.bottom) / ortCamera.zoom / element.Height, ortCamera.matrix);

                }
                else
                {

                    // camera neither orthographic nor perspective
                    console.warn("WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.");
                    this.enablePan = false;

                }

            }


            private void dollyOut(double dollyScale)
            {

                if (this._object is PerspectiveCamera)
                {

                    scale /= dollyScale;

                }
                else if (this._object is OrthographicCamera)
                {

                    this._object.zoom = Math.Max(this.minZoom, Math.Min(this.maxZoom, this._object.zoom * dollyScale));
                    this._object.updateProjectionMatrix();
                    zoomChanged = true;

                }
                else
                {

                    console.warn("WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.");
                    this.enableZoom = false;

                }

            }

            private void dollyIn(double dollyScale)
            {

                if (this._object is PerspectiveCamera)
                {

                    scale *= dollyScale;

                }
                else if (this._object is OrthographicCamera)
                {

                    this._object.zoom = Math.Max(this.minZoom, Math.Min(this.maxZoom, this._object.zoom / dollyScale));
                    this._object.updateProjectionMatrix();
                    zoomChanged = true;

                }
                else
                {

                    console.warn("WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.");
                    this.enableZoom = false;

                }

            }

            #region event callbacks - update the object state

            private void handleMouseDownRotate(MouseEventArgs e)
            {

                rotateStart.Set(e.X, e.Y);

            }
            private void handleMouseDownDolly(MouseEventArgs e)
            {

                dollyStart.Set(e.X, e.Y);

            }

            private void handleMouseDownPan(MouseEventArgs e)
            {

                panStart.Set(e.X, e.Y);

            }

            private void handleMouseMoveRotate(MouseEventArgs e)
            {

                rotateEnd.Set(e.X, e.Y);

                rotateDelta.SubVectors(rotateEnd, rotateStart).MultiplyScalar(this.rotateSpeed);

                var element = this.glControl;

                rotateLeft(2 * Math.PI * rotateDelta.X / element.Height); // yes, height

                rotateUp(2 * Math.PI * rotateDelta.Y / element.Height);

                rotateStart.Copy(rotateEnd);

                this.update();

            }

            private void handleMouseMoveDolly(MouseEventArgs e)
            {

                dollyEnd.Set(e.X, e.Y);

                dollyDelta.SubVectors(dollyEnd, dollyStart);

                if (dollyDelta.Y > 0)
                {

                    dollyOut(getZoomScale());

                }
                else if (dollyDelta.Y < 0)
                {

                    dollyIn(getZoomScale());

                }

                dollyStart.Copy(dollyEnd);

                this.update();

            }

            private void handleMouseMovePan(MouseEventArgs e)
            {

                panEnd.Set(e.X, e.Y);

                panDelta.SubVectors(panEnd, panStart).MultiplyScalar(this.panSpeed);

                pan(panDelta.X, panDelta.Y);

                panStart.Copy(panEnd);

                this.update();

            }

            private void handleMouseWheel(MouseEventArgs e)
            {

                if (e.Delta > 0)
                {

                    dollyIn(getZoomScale());

                }
                else if (e.Delta < 0)
                {

                    dollyOut(getZoomScale());

                }

                this.update();

            }

            private void handleKeyDown(KeyEventArgs e)
            {

                var needsUpdate = false;

                switch (e.KeyCode)
                {
                    case Keys.Up:
                        if (e.Control || e.Alt || e.Shift)
                            rotateUp(2 * Math.PI * this.rotateSpeed / this.glControl.Height);
                        else
                            pan(0, this.keyPanSpeed);
                        needsUpdate = true;
                        break;

                    case Keys.Down:
                        if (e.Control || e.Alt || e.Shift)
                            rotateUp(-2 * Math.PI * this.rotateSpeed / this.glControl.Height);
                        else
                            pan(0, -this.keyPanSpeed);
                        needsUpdate = true;
                        break;
                    case Keys.Left:
                        if (e.Control || e.Alt || e.Shift)
                            rotateLeft(2 * Math.PI * this.rotateSpeed / this.glControl.Height);
                        else
                            pan(this.keyPanSpeed, 0);
                        needsUpdate = true;
                        break;

                    case Keys.Right:
                        if (e.Control || e.Alt || e.Shift)
                            rotateLeft(-2 * Math.PI * this.rotateSpeed / this.glControl.Height);
                        else
                            pan(-this.keyPanSpeed, 0);
                        needsUpdate = true;
                        break;

                }

                if (needsUpdate)
                {
                    // prevent the browser from scrolling on cursor keys
                    //e.preventDefault();
                    this.update();
                }
            }

            private void handleTouchStartRotate()
            {

                //if (pointers.length == 1)
                //{

                //    rotateStart.set(pointers[0].pageX, pointers[0].pageY);

                //}
                //else
                //{

                //    var x = 0.5 * (pointers[0].pageX + pointers[1].pageX);
                //    var y = 0.5 * (pointers[0].pageY + pointers[1].pageY);

                //    rotateStart.set(x, y);

                //}

            }

            private void handleTouchStartPan()
            {

                //if (pointers.length == 1)
                //{

                //    panStart.set(pointers[0].pageX, pointers[0].pageY);

                //}
                //else
                //{

                //    var x = 0.5 * (pointers[0].pageX + pointers[1].pageX);
                //    var y = 0.5 * (pointers[0].pageY + pointers[1].pageY);

                //    panStart.set(x, y);

                //}

            }

            private void handleTouchStartDolly()
            {

                //var dx = pointers[0].pageX - pointers[1].pageX;
                //var dy = pointers[0].pageY - pointers[1].pageY;

                //var distance = MathEx.sqrt(dx * dx + dy * dy);

                //dollyStart.set(0, distance);

            }

            private void handleTouchStartDollyPan()
            {

                if (this.enableZoom) handleTouchStartDolly();

                if (this.enablePan) handleTouchStartPan();

            }

            private void handleTouchStartDollyRotate()
            {

                if (this.enableZoom) handleTouchStartDolly();

                if (this.enableRotate) handleTouchStartRotate();

            }

            private void handleTouchMoveRotate(object e)
            {

                //if (pointers.length == 1)
                //{

                //    rotateEnd.set(e.pageX, e.pageY);

                //}
                //else
                //{

                //    var position = getSecondPointerPosition(e);

                //    var x = 0.5 * (e.pageX + position.x);
                //    var y = 0.5 * (e.pageY + position.y);

                //    rotateEnd.set(x, y);

                //}
                //rotateDelta.subVectors(rotateEnd, rotateStart).multiplyScalar(this.rotateSpeed);

                //var element = this.glControl;

                //rotateLeft(2 * Math.PI * rotateDelta.x / element.Height); // yes, height

                //rotateUp(2 * Math.PI * rotateDelta.y / element.Height);

                //rotateStart.copy(rotateEnd);
            }

            private void handleTouchMovePan(object e)
            {

                //if (pointers.length == 1)
                //{

                //    panEnd.set(e.pageX, e.pageY);
                //}
                //else
                //{

                //    var position = getSecondPointerPosition(e);

                //    var x = 0.5 * (e.pageX + position.x);
                //    var y = 0.5 * (e.pageY + position.y);

                //    panEnd.set(x, y);

                //}

                //panDelta.subVectors(panEnd, panStart).multiplyScalar(this.panSpeed);

                //pan(panDelta.x, panDelta.y);

                //panStart.copy(panEnd);

            }

            private void handleTouchMoveDolly(object e)
            {

                //var position = getSecondPointerPosition(e);

                //var dx = e.pageX - position.x;
                //var dy = e.pageY - position.y;

                //var distance = MathEx.sqrt(dx * dx + dy * dy);

                //dollyEnd.set(0, distance);

                //dollyDelta.set(0, Math.Pow(dollyEnd.y / dollyStart.y, this.zoomSpeed));

                //dollyOut(dollyDelta.y);

                //dollyStart.copy(dollyEnd);

            }

            private void handleTouchMoveDollyPan(object e)
            {

                if (this.enableZoom) handleTouchMoveDolly(e);

                if (this.enablePan) handleTouchMovePan(e);

            }

            private void handleTouchMoveDollyRotate(object e)
            {

                if (this.enableZoom) handleTouchMoveDolly(e);

                if (this.enableRotate) handleTouchMoveRotate(e);

            }
            #endregion

            #region event handlers - FSM: listen for events and reset state
            private void onPointerDown(object e)
            {

                //if (this.enabled == false) return;

                //if (pointers.length == 0)
                //{

                //    this.glControl.setPointerCapture(e.pointerId);

                //    this.glControl.addEventListener("pointermove", onPointerMove);
                //    this.glControl.addEventListener("pointerup", onPointerUp);

                //}

                ////

                //addPointer(e);

                //if (e.pointerType == "touch")
                //{

                //    onTouchStart(e);

                //}
                //else
                //{

                //    onMouseDown(e);

                //}

            }

            private void onPointerMove(object e)
            {

                //if (this.enabled == false) return;

                //if (e.pointerType == "touch")
                //{

                //    onTouchMove(e);

                //}
                //else
                //{

                //    onMouseMove(e);

                //}

            }

            private void onPointerUp(object e)
            {

                //removePointer(e);

                //if (pointers.length == 0)
                //{

                //    this.glControl.releasePointerCapture(e.pointerId);

                //    this.glControl.removeEventListener("pointermove", onPointerMove);
                //    this.glControl.removeEventListener("pointerup", onPointerUp);

                //}

                //this.dispatchEvent(_endEvent);

                //state = STATE.NONE;

            }

            private void onPointerCancel(object e)
            {

                removePointer(e);

            }

            private void onMouseDown(object sender, MouseEventArgs e)
            {
                if (this.enabled == false) return;
                switch (e.Button)
                {

                    case MouseButtons.Middle:

                        if (this.enableZoom == false) return;

                        handleMouseDownDolly(e);

                        state = STATE.DOLLY;

                        break;

                    case MouseButtons.Left:

                        if (false) //(e.ctrlKey || e.metaKey || e.shiftKey)
                        {
                             
                            if (this.enablePan == false) return;

                            handleMouseDownPan(e);

                            state = STATE.PAN;

                        }
                        else
                        {

                            if (this.enableRotate == false) return;

                            handleMouseDownRotate(e);

                            state = STATE.ROTATE;

                        }

                        break;

                    case MouseButtons.Right:

                        if (false) //(e.ctrlKey || e.metaKey || e.shiftKey)
                        {

                            if (this.enableRotate == false) return;

                            handleMouseDownRotate(e);

                            state = STATE.ROTATE;

                        }
                        else
                        {

                            if (this.enablePan == false) return;

                            handleMouseDownPan(e);

                            state = STATE.PAN;

                        }

                        break;

                    default:
                        state = STATE.NONE;
                        break;

                }

                if (state != STATE.NONE)
                {

                    this.dispatchEvent(_startEvent);

                }

            }
            private void onMouseUp(object sender, MouseEventArgs e)
            {
                if (this.enabled == false) return;
                state = STATE.NONE;

                this.dispatchEvent(_endEvent);
            }

            private void onMouseMove(object sender, MouseEventArgs e)
            {
                if (this.enabled == false) return;
                switch (state)
                {

                    case STATE.ROTATE:

                        if (this.enableRotate == false) return;

                        handleMouseMoveRotate(e);

                        break;

                    case STATE.DOLLY:

                        if (this.enableZoom == false) return;

                        handleMouseMoveDolly(e);

                        break;

                    case STATE.PAN:

                        if (this.enablePan == false) return;

                        handleMouseMovePan(e);

                        break;

                }

            }

            private void onMouseWheel(object sender, MouseEventArgs e)
            {

                if (this.enabled == false || this.enableZoom == false || state != STATE.NONE) return;

                //e.preventDefault();

                this.dispatchEvent(_startEvent);

                handleMouseWheel(e);

                this.dispatchEvent(_endEvent);

            }

            private void onKeyDown(object sender, KeyEventArgs e)
            {

                if (this.enabled == false || this.enablePan == false) return;

                handleKeyDown(e);

            }

            private void onTouchStart(object e)
            {

                //trackPointer(e);

                //switch (pointers.length)
                //{

                //    case 1:

                //        switch (this.touches.ONE)
                //        {

                //            case TOUCH.ROTATE:

                //                if (this.enableRotate == false) return;

                //                handleTouchStartRotate();

                //                state = STATE.TOUCH_ROTATE;

                //                break;

                //            case TOUCH.PAN:

                //                if (this.enablePan == false) return;

                //                handleTouchStartPan();

                //                state = STATE.TOUCH_PAN;

                //                break;

                //            default:

                //                state = STATE.NONE;

                //        }

                //        break;

                //    case 2:

                //        switch (this.touches.TWO)
                //        {

                //            case TOUCH.DOLLY_PAN:

                //                if (this.enableZoom == false && this.enablePan == false) return;

                //                handleTouchStartDollyPan();

                //                state = STATE.TOUCH_DOLLY_PAN;

                //                break;

                //            case TOUCH.DOLLY_ROTATE:

                //                if (this.enableZoom == false && this.enableRotate == false) return;

                //                handleTouchStartDollyRotate();

                //                state = STATE.TOUCH_DOLLY_ROTATE;

                //                break;

                //            default:

                //                state = STATE.NONE;

                //        }

                //        break;

                //    default:

                //        state = STATE.NONE;

                //}

                //if (state !== STATE.NONE)
                //{

                //    this.dispatchEvent(_startEvent);

                //}

            }

            private void onTouchMove(object e)
            {

                trackPointer(e);

                switch (state)
                {

                    case STATE.TOUCH_ROTATE:

                        if (this.enableRotate == false) return;

                        handleTouchMoveRotate(e);

                        this.update();

                        break;

                    case STATE.TOUCH_PAN:

                        if (this.enablePan == false) return;

                        handleTouchMovePan(e);

                        this.update();

                        break;

                    case STATE.TOUCH_DOLLY_PAN:

                        if (this.enableZoom == false && this.enablePan == false) return;

                        handleTouchMoveDollyPan(e);

                        this.update();

                        break;

                    case STATE.TOUCH_DOLLY_ROTATE:

                        if (this.enableZoom == false && this.enableRotate == false) return;

                        handleTouchMoveDollyRotate(e);

                        this.update();

                        break;

                    default:

                        state = STATE.NONE;
                        break;

                }

            }

            private void onContextMenu(object sender, System.EventArgs e)
            {

                if (this.enabled == false) return;

                //e.preventDefault();

            }

            private void addPointer(object e)
            {

                pointers.Push(e);

            }

            private void removePointer(object e)
            {

                //delete pointerPositions[e.pointerId];

                //for (let i = 0; i < pointers.length; i++)
                //{

                //    if (pointers[i].pointerId == e.pointerId)
                //    {

                //        pointers.splice(i, 1);
                //        return;

                //    }

                //}

            }

            private void trackPointer(object e)
            {

                //var position = pointerPositions[e.pointerId];

                //if (position == null)
                //{

                //    position = new Vector2();
                //    pointerPositions[e.pointerId] = position;

                //}

                //position.set(e.pageX, e.pageY);

            }

            private void getSecondPointerPosition(object e)
            {

                //var pointer = (e.pointerId == pointers[0].pointerId) ? pointers[1] : pointers[0];

                //return pointerPositions[pointer.pointerId];

            }
            #endregion
        }
}
